package com.radar.algorithm;

import com.radar.common.Const;
import com.radar.common.GisConstants;
import com.radar.common.ProductType;
import com.radar.exception.RadarCaclError;
import org.meteoinfo.global.MIMath;
import org.meteoinfo.global.PointD;
import org.meteoinfo.projection.Reproject;
import ucar.ma2.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MathCommon {

    public static double[] getMaxMinValue(double[][] data) {
        double max = 0.0D;
        double min = 0.0D;
        int vdNum = 0;

        for (int i = 0; i < data.length; ++i) {
            for (int j = 0; j < data[0].length; ++j) {
                if (!java.lang.Double.isNaN(data[i][j]) && data[i][j] != GisConstants.UNDEF) {
                    if (vdNum == 0) {
                        min = data[i][j];
                        max = min;
                    } else {
                        if (min > data[i][j]) {
                            min = data[i][j];
                        }
                        if (max < data[i][j]) {
                            max = data[i][j];
                        }
                    }
                    ++vdNum;
                }
            }
        }

        return new double[]{max, min};
    }

    public static double avg(double[][] data) {
        double max = 0.0D;
        int s = 0;
        for (int i = 0; i < data.length; ++i) {
            for (int j = 0; j < data[0].length; ++j) {
                if (!java.lang.Double.isNaN(data[i][j]) && data[i][j] != GisConstants.UNDEF) {
                    max += data[i][j];
                    s++;
                }
            }
        }
        return max / s;
    }

    public static double sum(double[] data) {
        double max = 0.0D;
        for (int i = 0; i < data.length; ++i) {
            if (!java.lang.Double.isNaN(data[i]) && data[i] != GisConstants.UNDEF) {
                max += data[i];
            }
        }
        return max;
    }

    public static long sum(double[][] data) {
        long max = 0;
        for (int i = 0; i < data.length; ++i) {
            for (int j = 0; j < data[0].length; ++j) {
                if (!java.lang.Double.isNaN(data[i][j]) && data[i][j] != GisConstants.UNDEF) {
                    max += data[i][j];
                }
            }
        }
        return max;
    }

    public static double[] getMaxMinValue(double[] data) throws RadarCaclError {
        if (data == null || data.length == 0) {
            throw new RadarCaclError("data length Incorrect");
        }
        double max = 0.0D;
        double min = 0.0D;
        int vdNum = 0;

        for (int i = 0; i < data.length; ++i) {
            if (!java.lang.Double.isNaN(data[i]) && data[i] != GisConstants.UNDEF) {
                if (vdNum == 0) {
                    min = data[i];
                    max = min;
                } else {
                    if (min > data[i]) {
                        min = data[i];
                    }
                    if (max < data[i]) {
                        max = data[i];
                    }
                }
                ++vdNum;
            }
        }

        return new double[]{max, min};
    }

    /**
     * Get the angle between latitude and longitude
     *
     * @param lat_a
     * @param lng_a
     * @param lat_b
     * @param lng_b
     * @return
     */
    public static double getAngle(double lng_a, double lng_b, double lat_a, double lat_b) {
        double y = Math.sin(lng_b - lng_a) * Math.cos(lat_b);
        double x = Math.cos(lat_a) * Math.sin(lat_b) - Math.sin(lat_a) * Math.cos(lat_b) * Math.cos(lng_b - lng_a);
        double brng = Math.atan2(y, x);
        brng = Math.toDegrees(brng);
        if (brng < 0)
            brng = brng + 360;
        return brng;

    }

    /**
     * Get the distance through the latitude and longitude of point AB
     *
     * @param x
     * @param x1
     * @param y
     * @param y1
     * @return
     */
    public static double getDistance(double x, double x1, double y, double y1) {
        if (x == x1 && y == y1) {
            return 0;
        }
        double earthRadius = 6371393;
        double radiansAX = Math.toRadians(x);
        double radiansAY = Math.toRadians(y);
        double radiansBX = Math.toRadians(x1);
        double radiansBY = Math.toRadians(y1);
        double cos = Math.cos(radiansAY) * Math.cos(radiansBY) * Math.cos(radiansAX - radiansBX)
                + Math.sin(radiansAY) * Math.sin(radiansBY);
        double acos = Math.acos(cos);
        return earthRadius * acos;
    }

    public static double getDistanceKM(double x, double x1, double y, double y1) {
        double distance = getDistance(x, x1, y, y1);
        if (distance != 0) {
            distance = distance / 1000.0;
        }
        return distance;
    }

    /**
     * 经纬度点集合 等分距离插值到 num个 粗略算法 没有投影计算
     *
     * @param points
     * @param num
     * @return
     */
    public static List<PointD> getDisPoint(List<PointD> points, int num) {
        if (points.size() < 2) {
            return null;
        }
        List<PointD> rlist = new ArrayList<>();
        for (int i = 0; i < points.size(); i++) {
            rlist.add(Reproject.reprojectPoint(points.get(i).X, points.get(i).Y, GisConstants.lonlat, GisConstants.merc));
        }
        double dissum = 0;
        for (int i = 1; i < rlist.size(); i++) {
            dissum += Math.sqrt(Math.pow(Math.abs(rlist.get(i - 1).X - rlist.get(i).X), 2) + Math.pow(Math.abs(rlist.get(i - 1).Y - rlist.get(i).Y), 2));
        }
        double step = dissum / num;
        List<PointD> list = new ArrayList<>();
        for (int i = 1; i < rlist.size(); i++) {
            double distance = Math.sqrt(Math.pow(Math.abs(rlist.get(i - 1).X - rlist.get(i).X), 2) + Math.pow(Math.abs(rlist.get(i - 1).Y - rlist.get(i).Y), 2));
            int numI = (int) (distance / step == 0 ? 1 : distance / step) - 1;
            double stepx = (rlist.get(i).X - rlist.get(i - 1).X) / (numI);
            double stepy = (rlist.get(i).Y - rlist.get(i - 1).Y) / (numI);
            if (i == rlist.size() - 1) {
                numI++;
            }
            for (int j = 0; j < numI - 1; j++) {
                org.meteoinfo.global.PointD pointD = Reproject.reprojectPoint(rlist.get(i - 1).X + stepx * j, rlist.get(i - 1).Y + stepy * j, GisConstants.merc, GisConstants.lonlat);
                list.add(new PointD(pointD.X, pointD.Y));
            }
        }
        return list;
    }

    /**
     * 两点线性插值
     *
     * @param V1
     * @param V2
     * @param R1
     * @param R2
     * @param R
     * @return
     */
    public static double linearInterpolate(double V1, double V2, double R1, double R2, double R) {
        if (R1 == R) {
            return V1;
        }
        if (R2 == R) {
            return V2;
        }
        if (V1 == GisConstants.UNDEF || V2 == GisConstants.UNDEF) {
            return GisConstants.UNDEF;
        }
        double ret = V1 + (V2 - V1) * (R - R1) / (R2 - R1);
        return ret;
    }

    public static double getAngleXY(double y, double x, double lat, double lon) {
        double angle = Math.atan2((y - lat), (x - lon));
        angle = 360 - angle / Const.DEG2RAD;
        angle += 90;
        angle = angle > 360 ? angle - 360 : angle;
        return angle;
    }

    public static ArrayDouble toArray(double[] d) {
        int index = 0;
        ArrayDouble arrayDouble = new ArrayDouble.D1(d.length);
        for (int i = 0; i < d.length; i++) {
            arrayDouble.setDouble(i, d[i]);
        }
        return arrayDouble;
    }

    public static ArrayDouble toArray(double[][] d) {
        int index = 0;
        ArrayDouble arrayDouble = new ArrayDouble.D2(d.length, d[0].length);
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                arrayDouble.setDouble(index, d[i][j]);
                index++;
            }
        }
        return arrayDouble;
    }

    public static ArrayDouble toArray(double[][][] d) {
        int index = 0;
        ArrayDouble arrayDouble = new ArrayDouble.D3(d.length, d[0].length, d[0][0].length);
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                for (int k = 0; k < d[0][0].length; k++) {
                    arrayDouble.setDouble(index, d[i][j][k]);
                    index++;
                }
            }
        }
        return arrayDouble;
    }


    public static ArrayByte toByteArray(byte[] d) {
        int index = 0;
        ArrayByte arrayByte = new ArrayByte.D1(d.length);
        for (int i = 0; i < d.length; i++) {
            arrayByte.setByte(i, d[i]);
        }
        return arrayByte;
    }

    public static ArrayByte toByteArray(byte[][] d) {
        int index = 0;
        ArrayByte arrayByte = new ArrayByte.D2(d.length, d[0].length);
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                arrayByte.setByte(index, (byte) (0xff & d[i][j]));
                index++;
            }
        }
        return arrayByte;
    }

    public static ArrayByte toByteArray(byte[][][] d) {
        int index = 0;
        ArrayByte arrayByte = new ArrayByte.D3(d.length, d[0].length, d[0][0].length);
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                for (int k = 0; k < d[0][0].length; k++) {
                    arrayByte.setByte(index, d[i][j][k]);
                    index++;
                }
            }
        }
        return arrayByte;
    }


    public static ArrayShort toShortArray(short[] d) {
        int index = 0;
        ArrayShort arrayByte = new ArrayShort.D1(d.length);
        for (int i = 0; i < d.length; i++) {
            arrayByte.setShort(i, d[i]);
        }
        return arrayByte;
    }

    public static ArrayShort toShortArray(short[][] d) {
        int index = 0;
        ArrayShort arrayByte = new ArrayShort.D2(d.length, d[0].length);
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                arrayByte.setShort(index, d[i][j]);
                index++;
            }
        }
        return arrayByte;
    }

    public static ArrayShort toShortArray(short[][][] d) {
        int index = 0;
        ArrayShort arrayByte = new ArrayShort.D3(d.length, d[0].length, d[0][0].length);
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                for (int k = 0; k < d[0][0].length; k++) {
                    arrayByte.setShort(index, d[i][j][k]);
                    index++;
                }
            }
        }
        return arrayByte;
    }


    public static ArrayFloat toFloatArray(float[] d) {
        int index = 0;
        ArrayFloat arrayDouble = new ArrayFloat.D1(d.length);
        for (int i = 0; i < d.length; i++) {
            arrayDouble.setDouble(i, d[i]);
        }
        return arrayDouble;
    }

    public static ArrayFloat toFloatArray(float[][] d) {
        int index = 0;
        ArrayFloat arrayDouble = new ArrayFloat.D2(d.length, d[0].length);
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                arrayDouble.setDouble(index, d[i][j]);
                index++;
            }
        }
        return arrayDouble;
    }

    public static ArrayFloat toFloatArray(float[][][] d) {
        int index = 0;
        ArrayFloat arrayDouble = new ArrayFloat.D3(d.length, d[0].length, d[0][0].length);
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                for (int k = 0; k < d[0][0].length; k++) {
                    arrayDouble.setDouble(index, d[i][j][k]);
                    index++;
                }
            }
        }
        return arrayDouble;
    }


    public static double[] floatToDouble(float[] f) {
        double[] d = new double[f.length];
        for (int i = 0; i < d.length; i++) {
            d[i] = f[i];
        }
        return d;
    }

    public static double[][] floatToDouble(float[][] f) {
        double[][] d = new double[f.length][f[0].length];
        for (int i = 0; i < d.length; i++) {
            d[i] = floatToDouble(f[i]);
        }
        return d;
    }

    public static double[][][] floatToDouble(float[][][] f) {
        double[][][] d = new double[f.length][f[0].length][f[0][0].length];
        for (int i = 0; i < d.length; i++) {
            d[i] = floatToDouble(f[i]);
        }
        return d;
    }

    public static float[] doubleToFloat(double[] f) {
        float[] d = new float[f.length];
        for (int i = 0; i < d.length; i++) {
            d[i] = (float) f[i];
        }
        return d;
    }

    public static float[][] doubleToFloat(double[][] f) {
        float[][] d = new float[f.length][f[0].length];
        for (int i = 0; i < d.length; i++) {
            d[i] = doubleToFloat(f[i]);
        }
        return d;
    }

    public static float[][][] doubleToFloat(double[][][] f) {
        float[][][] d = new float[f.length][f[0].length][f[0][0].length];
        for (int i = 0; i < d.length; i++) {
            d[i] = doubleToFloat(f[i]);
        }
        return d;
    }


    public static void fill(Array d) {
        for (int i = 0; i < d.getSize(); i++) {
            d.setDouble(i, GisConstants.UNDEF);
        }
    }

    public static void fillByte(Array d) {
        byte b = -128;
        for (int i = 0; i < d.getSize(); i++) {
            d.setByte(i, b);
        }
    }

    public static void fill(double[][] d) {
        for (int i = 0; i < d.length; i++) {
            Arrays.fill(d[i], GisConstants.UNDEF);
        }
    }

    public static void fill(double[] d) {
        Arrays.fill(d, GisConstants.UNDEF);
    }

    public static void fill(double[][][] d) {
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                Arrays.fill(d[i][j], GisConstants.UNDEF);
            }
        }
    }

    public static void fill(float[][] d) {
        for (int i = 0; i < d.length; i++) {
            Arrays.fill(d[i], GisConstants.UNDEF);
        }
    }

    public static void fill(float[] d) {
        Arrays.fill(d, GisConstants.UNDEF);
    }

    public static void fill(float[][][] d) {
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                Arrays.fill(d[i][j], GisConstants.UNDEF);
            }
        }
    }

    public static void fill(byte[][] d) {
        for (int i = 0; i < d.length; i++) {
            Arrays.fill(d[i], (byte) -128);
        }
    }

    public static void fill(byte[] d) {
        Arrays.fill(d, (byte) -128);
    }

    public static void fill(byte[][][] d) {
        for (int i = 0; i < d.length; i++) {
            for (int j = 0; j < d[0].length; j++) {
                Arrays.fill(d[i][j], (byte) -128);
            }
        }
    }


    public static double[][] decode(byte[][] data, ProductType type) {
        double[][] c = new double[data.length][data[0].length];
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length; j++) {
                c[i][j] = Decode(data[i][j], type);
            }
        }
        return c;
    }

    public static double[][][] decode(byte[][][] data, ProductType type) {
        double[][][] c = new double[data.length][data[0].length][data[0].length];
        for (int i = 0; i < data.length; i++) {
            c[i] = decode(data[i], type);
        }
        return c;
    }

    public static double[][] decodeCal(short[][] data) {
        double[][] c = new double[data.length][data[0].length];
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length; j++) {
                c[i][j] = data[i][j] * 0.01;
            }
        }
        return c;
    }

    public static double Decode(byte code, ProductType type) {
        if (type == ProductType.SW || type == ProductType.VEL) {
            return DecodeV(code);
        }else {
            return DecodeRef(code);
        }
    }

    public static double DecodeRef(byte code) {
        if (code == -128) {
            return GisConstants.UNDEF;
        }
        return (int) code / 2.0 + 32.5;
    }

    public static double DecodeV(byte code) {
        if (code == -128) {
            return GisConstants.UNDEF;
        }
        return (int) code;
    }

    // -32.5 - 95.5
    public static byte CodeRef(double code) {
        if (MIMath.doubleEquals(code, GisConstants.UNDEF)) {
            return -128;
        }
        return (byte) ((int) ((code - 32.5) * 2));
    }

    public static byte CodeV(double code) {
        if (MIMath.doubleEquals(code, GisConstants.UNDEF)) {
            return -128;
        }
        return (byte) ((int) (code * 2));
    }

    public static byte Code(double code, ProductType type) {
        if (type == ProductType.SW || type == ProductType.VEL) {
            return CodeV(code);
        } else {
            return CodeRef(code);
        }
    }

    public static byte[][] Code(double[][] code, ProductType type) {
        byte[][] data = new byte[code.length][code[0].length];
        for (int i = 0; i < code.length; i++) {
            for (int j = 0; j < code[0].length; j++) {
                data[i][j] = Code(code[i][j], type);
            }
        }
        return data;
    }

    public static byte[][][] Code(double[][][] code, ProductType type) {
        byte[][][] data = new byte[code.length][code[0].length][code[0][0].length];
        for (int i = 0; i < code.length; i++) {
            data[i] = Code(code[i], type);
        }
        return data;
    }

    public static void main(String[] args) {
        System.out.println(CodeRef(60));
        System.out.println(CodeRef(-33));
        System.out.println(CodeRef(-30));
        System.out.println(CodeRef(90));

        System.out.println(0xff & CodeRef(60));
        System.out.println(0xff & CodeRef(-33));
        System.out.println(0xff & CodeRef(-30));
        System.out.println(0xff & CodeRef(90));

        System.out.println(DecodeRef(CodeRef(60)));
        System.out.println(DecodeRef(CodeRef(-33)));
        System.out.println(DecodeRef(CodeRef(-30)));
        System.out.println(DecodeRef(CodeRef(90)));
    }
}
